/* ************************************************************************** *
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 * 
 * This file is licensed to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 * ************************************************************************** */
package org.statewalker.fsm;

import java.util.Iterator;

import org.statewalker.graph.GraphIterator;
import org.statewalker.graph.IWalkerListener;
import org.statewalker.graph.Walker;
import org.statewalker.graph.WalkerListener;

/**
 * This is an implementation of a Hierarchical Finite State Machine (HFSM).
 * <p>
 * The model is the following: 1) The system has exactly one active state. 2)
 * The system changes its state when a new event is occurred. 3) Each state has
 * exactly one parent state (except the topmost, root, state). 4) Transitions
 * between states are defined in the parent state.
 * </p>
 * <p>
 * This class is an iterator over states of a system. Every time when a new
 * event occurred the client of this class should call the
 * {@link #setEvent(Object)} method and after that call the {@link #hasNext()}/
 * {@link #next()} methods.
 * </p>
 * <p>
 * This class uses {@link IFsmStateListener} instances to change system states.
 * </p>
 * <p>
 * This class notifies registered listeners when the system changes its state.
 * The process itself does not perform any actions for each state. It is up to
 * the registered listener to define what should happen when the system enters
 * in or goes out of a state.
 * </p>
 * 
 * @author kotelnikov
 * @param <S> the state type
 * @param <E> the type of events initializing transitions betwenn states
 */
public class FsmProcess<S, E> extends GraphIterator<S> {

    /**
     * The current event activating transitions between substates of the
     * currently active parent state.
     */
    private E fEvent;

    /**
     * This provider encapsulates the state loading strategy. It could use
     * "hard-coded" state transitions or it could be based on external
     * configurations to how the system should change state.
     */
    protected IFsmStateProvider<S, E> fProvider;

    /**
     * Listeners are used to notify about transitions between states. They are
     * used also to notify when each state is activated/de-activated. Individual
     * listeners could perform some actions when the process
     * activates/de-activates individual states.
     */
    protected IFsmStateListener<S, E> fStateListener;

    /**
     * This is an internal {@link IWalkerListener} object translating calls from
     * the internal {@link Walker} about activation/deactivation of individual
     * graph nodes into calls about activation/de-activation of states.
     */
    private IWalkerListener<S> fWalkerListener = new WalkerListener<S>() {

        /**
         * @see org.statewalker.graph.WalkerListener#onBegin(java.lang.Object,
         *      java.lang.Object)
         */
        @Override
        public void onBegin(S parent, S node) {
            fStateListener.onActivate(parent, node, fEvent);
        }

        /**
         * @see org.statewalker.graph.WalkerListener#onEnd(java.lang.Object,
         *      java.lang.Object)
         */
        @Override
        public void onEnd(S parent, S node) {
            fStateListener.onDeactivate(parent, node, fEvent);
        }

        /**
         * @see org.statewalker.graph.WalkerListener#onTransition(java.lang.Object,
         *      java.lang.Object, java.lang.Object)
         */
        @Override
        public void onTransition(S parent, S prev, S next) {
            fStateListener.onTransition(parent, prev, fEvent, next);
        }

    };

    /**
     * This constructor activates internal fields and sets the state provider
     * strategy and the listener which is notified when the system changes
     * states.
     * 
     * @param provider the state provider; it is a strategy defining new states
     *        when a new event is occurred.
     * @param listener this listener is notified about of
     *        activation/de-activation of individual states.
     */
    public FsmProcess(
        IFsmStateProvider<S, E> provider,
        IFsmStateListener<S, E> listener) {
        this(provider, listener, ALL);
    }

    /**
     * This constructor activates internal fields and sets the state provider
     * strategy and the listener which is notified when the system changes
     * states.
     * 
     * @param provider the state provider; it is a strategy defining new states
     *        when a new event is occurred.
     * @param listener this listener is notified about of
     *        activation/de-activation of individual states.
     * @param mask the mask defining when this state iterator returns the
     *        control to the caller. See the {@link #setMode(int)} for more
     *        information.
     */
    public FsmProcess(
        IFsmStateProvider<S, E> provider,
        IFsmStateListener<S, E> listener,
        int mask) {
        super((Walker<S>) null, mask);
        fWalker = new Walker<S>(fWalkerListener);
        fProvider = provider;
        fStateListener = listener;
    }

    /**
     * Returns the current active state.
     * 
     * @return the current active state
     */
    public S getCurrent() {
        return fWalker.getCurrent();
    }

    /**
     * Returns the current event associated with this process.
     * 
     * @return the current event associated with this process
     */
    public E getEvent() {
        return fEvent;
    }

    /**
     * Returns an iterator over all states in the internal stack.
     * 
     * @param direct if this flag is <code>true</code> then this method returns
     *        an direct iterator over states (parent states before child
     *        states); otherwise the returned iterator returns the children and
     *        after that the parent states.
     * @return an iterator over all states in the internal stack
     */
    public Iterator<S> getStateStack(boolean direct) {
        return fWalker.getStackIterator(direct);
    }

    /**
     * Returns <code>true</code> if the last performed step activated a new
     * state; if this method returns <code>false</code> then the last operation
     * performed by this process was the state de-activation.
     * 
     * @return <code>true</code> if the last performed step activated a new
     *         state; if this method returns <code>false</code> then the last
     *         operation performed by this process was the state de-activation.
     */
    public boolean isEntered() {
        return fWalker.isEntered();
    }

    /**
     * Returns <code>true</code> if this process is finished (ie the internal
     * stack is empty and there is no new states).
     * 
     * @return <code>true</code> if this process is finished (ie the internal
     *         stack is empty and there is no new states)
     */
    public boolean isFinished() {
        return fWalker.isFinished();
    }

    /**
     * This method uses the internal {@link IFsmStateProvider} instance to load
     * new state.
     * 
     * @see org.statewalker.graph.GraphIterator#loadNextNode(java.lang.Object,
     *      java.lang.Object)
     */
    @Override
    protected S loadNextNode(S parent, S prev) {
        S next = null;
        if (fWalker.getCurrent() == null) {
            next = fProvider.getRootState(fEvent);
        } else {
            next = fProvider.getNextState(parent, prev, fEvent);
        }
        return next;
    }

    /**
     * This method sets a new event; this event will be used to load the next
     * state when the {@link #hasNext()}/{@link #next()} methods are called.
     * 
     * @param event the event to set
     */
    public void setEvent(E event) {
        fEvent = event;
        prepareNextNode();
    }

}
